/*
 * myftserver.c
 * CS 3251 Programming Assignment #2
 * Dustin Shiflett
 */ 

#include <stdio.h>      /* for printf() and fprintf() */
#include <sys/socket.h> /* for socket(), bind, and connect() */
#include <arpa/inet.h>  /* for sockaddr_in and inet_ntoa() */
#include <stdlib.h>     /* for atoi() and exit() */
#include <string.h>     /* for memset() */
#include <unistd.h>     /* for close() and getpid() and alarm()*/
#include <fcntl.h>      /* for fcntl() */
#include <sys/file.h>   /* for O_NONBLOCK and FASYNC */
#include <signal.h>     /* for signal(), sigaction() and SIGALRM */
#include <errno.h>      /* for errno and EINTR*/
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/time.h>

#include "dropper.h"
#include "helperfuncs.h"

#define TIMEOUT_SECS    2       /* Seconds between retransmits */

int debug;
int tries = 0;   /* Count of times sent - GLOBAL for signal-handler access */
int sock = -1;
static struct sockaddr_in cliAddr;  /* Address of datagram source */
static unsigned int cliLen = sizeof(cliAddr);             /* Address length */
FILE file = NULL;
extern int errno;
struct sigaction int_handler;       /* For setting signal handler */
struct sigaction alrm_handler;

void DieWithError(char *errorMessage);  /* Error handling function */
void InterruptSignalHandler(int signalType); /* Interrupt signal handling function */
void catchAlarm(int ignored) {           /* Handler for SIGALRM */
    tries += 1;
}

int transmit(void* buff, int len) {
    int ret;
    /*UDP sends message in one datagram b/c it preserves msg boundaries, so no loop*/
    if (-1 == (ret = sendto_dropper(sock, (void*) buff, len, 0, (struct sockaddr*) &cliAddr, cliLen))) {
        printf("Error sending in transmit function.\n");
        fflush(stdout);
    } else if (ret != len) {
        printf("Transmit function didn't send everything.\n");
        fflush(stdout);
        ret = -1;
    }
    return ret;
}

int main(int argc, char *argv[]) {
    
    int listenPort, lossPercent, opt, returnCode, windowSize, packetSize, fileLength;
    char* fileName;
    struct sockaddr_in servAddr;
    
    debug = 0;
    /* check command line args: ./myftserver <port#> <loss%> */
    if((argc < 3) || (argc > 4)) {
        printf("usage: %s <Server Port> <Loss Percentage> [-d] \nServer Port: 1024-65535\nLoss Percentage: 0-100\n-d: Debug/Verbose Output\n", argv[0]);
    exit(1);
    }
    
    /*check for debug switch*/
    if(argc == 4) {
        if (strcmp("-d", argv[3]) == 0)
            debug = 1;
    }
    
    /*check for type constraints on parameters*/
    listenPort = atoi(argv[1]);
    if(listenPort > 65535 || listenPort < 1024) {
        printf("%s: You attempted to use %i (or a non-integer) as the server port. Allowed ports are between 1024 and 65535. (Ports from 0 to 1023 are reserved for superusers only and this program does not want to make them angry.)\n",argv[0], listenPort);
        exit(1);
    }
    if (debug) printf("Desired port acknowledged as %i.\n", listenPort);
    
    lossPercent = atoi(argv[2]);
    if(lossPercent < 0 || lossPercent > 100) {
        printf("%s: You attempted to use %i (or a non-integer) as the loss percentage. The loss percentage parameter should be a value between 0 and 100.\n", argv[0], lossPercent);
        exit(1);
    }
    if (debug) printf("Desired loss percentage acknowledged as %i.\n", lossPercent);
    
    
    if (debug) printf("Command line parameters accepted. Debug/verbose option enabled.\n");
    /*Finished checking command line input arguments*/
    
    /* Set InterruptSignalHandler() as handler function */
    int_handler.sa_handler = InterruptSignalHandler;
    /* Create mask that mask all signals */
    if (sigfillset(&int_handler.sa_mask) < 0)
        DieWithError("sigfillset() failed");
    /* No flags */
    int_handler.sa_flags = 0;
    /* Set signal handling for interrupt signals */
    if (sigaction(SIGINT, &int_handler, 0) < 0)
        DieWithError("sigaction() failed");
    
    /* Set signal handler for alarm signal */
    alrm_handler.sa_handler = catchAlarm;
    if (sigfillset(&alrm_handler.sa_mask) < 0) /* block everything in handler */
        DieWithError("sigfillset() failed");
    myAction.sa_flags = 0;

    if (sigaction(SIGALRM, &alrm_handler, 0) < 0)
        DieWithError("sigaction() failed for SIGALRM");
    
    
    /*time to create our socket*/
    if (debug) printf("Attempting to create a socket...\n");
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0) {
        printf("%s: cannot open socket! \n", argv[0]);
        exit(1);
    }
    if (debug) printf("Socket creation successful.\n");
    
    opt = 1;
    returnCode = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    if (returnCode < 0) {
        printf("%s: Could not set SO_REUSEADDR correctly.\nThe return code was %i.\n", argv[0], returnCode);
        exit(1);
    }
    if (debug) printf("Socket option SO_REUSEADDR set to 1(true).\n");
    
    /*attempt to bind to a local server port*/
    if (debug) printf("Attempting to bind to port %i...\n", listenPort);
    
    /*Zero out then set-up the server address structure*/
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(listenPort);
    returnCode = bind (sock, (struct sockaddr *) &servAddr, sizeof(servAddr));
    if(returnCode < 0) {
        printf("%s: cannot bind port number %i \nThe return code was %i.\n", argv[0], listenPort, returnCode);
        closeSocket(sock);
        fflush(stdout);
        exit(1);
    }
    
    if (debug) printf("Port %i successfully bound.\n", listenPort);
    /*socket is set-up*/
    
    printf("%s: listening on UDP port %i...\n",
       argv[0], listenPort);
    
    /*time for infinite loop listen for requests, (re)send packets, listen for ACKs*/
    while(1) {
        clientHandler();
    }
    
    /*PAST THE INFINITE LOOP--NOTHING WILL BE RUN*/
    return 1;
}

void InterruptSignalHandler(int signalType)
{
    printf("Interrupt Received.  Exiting program.\n");
    closeSocket(sock);
    exit(1);
}

int clientHandler() {
    initPacket clientParams;
    initReply responsePacket;
    ackPacket ack;
    unsigned int ackSeqNum, seqNum, newSeqNum, progIDCheck, fileSize;
    int recvMsgSize, totalSize;           /* Size of datagram */
    char recvBuffer[MAX_BUFFER_SIZE];     /* Datagram buffer */
    char workBuffer[MAX_BUFFER_SIZE];
    int clientInit = 0; /*Client session active*/
    int waitingForFinalAck = 0;
    int sanityFlag = 0;
    int rv = -1;
    int success = 0;
    
    /*Set default doing nothing state*/
    flushbuffer();
    fclose(file);
    file = NULL;
    clientInit = 0;
    ackSeqNum = seqNum = newSeqNum = 0;
    errno = 0;
    totalMsgSize = recvMsgSize = 0;
    tries = 0;
    
    while(!sanityFlag) {
        if (SIG_ERR == signal(SIGALRM, catchAlarm)) {
            printf("SIGALRM trap failed to set.\n");
            fflush(stdout);
        } else {
            alarm(MAX_ALARM_TIME);
        }
        /*block until data is received*/
        recvMsgSize = recvfrom(sock, recvBuffer, sizeof(initPacket), 0, (struct sockaddr*) &cliAddr, &cliLen);
        
        if (recvMsgSize < 0) {
            daprint("Socket Error while possibly receiving Init packet: %i\n", recvMsgSize);
        } else if (recvMsgSize != sizeof(initPacket)) {
            /*Didn't get an entire initPacket, so ignore it*/
        } else {
            /*looks like we have our initPacket!*/
            memcpy((void*) &clientParams, (void*) recvBuffer, recvMsgSize);
            /*find out what the client wants*/
            progIDCheck = ntohl(clientParams.progID);
            newSeqNum = ntohl(clientParams.seqNum);
            if (progIDCheck == 901841089) {
                dprint("Program ID verified from client init packet.\n");
                if (newSeqNum == 0) {
                    /*Assume we have a sane initPacket*/
                    dprint("Sequence number verified as 0.\n");
                    sanityFlag = 1;
                }
            }
        }
    }
    
    if((clientParams.maxPacketSize > MAX_WINDOW_SIZE) || (clientParams.windowSize > MAX_WINDOW_SIZE) || (clientParams.maxPacketSize < MIN_WINDOW_SIZE) || (clientParams.windowSize < MIN_WINDOW_SIZE)) {
        printf("The window or packet size requested by the client is not acceptable.\nIgnoring attempt to connect.\n");
        return -1;
    }
    
    if(fileSize = fileExists((char*) clientParams.fileName)) {
        /*The requested file exists, so client is initialized!*/
        clientInit = 1;
        daprint("Requested file ('%s') exists and can be read.\n", clientParams.fileName);
        responsePacket.fileSize = fileSize;
    } else {
        printf("Server cannot open the file '%s' requested by the client.\nWill send a negative reply to the client.\n", clientParams.fileName);
        responsePacket.fileSize = 0;
    }
    
    responsePacket.progID = htonl(901841089);
    responsePacket.seqNum = htonl(1);
    
    do {
        if (-1 == (rv = transmit(&responsePacket, sizeof(initReply)))) {
        success = 0;
        daprint("Failed to send init response packet: %i.\n", rv);
    } else {
        dprint("Success packet transmitted successfully.\n");
        alarm(MAX_ALARM_TIME);
        rv = recvfrom(sock, recvBuffer, sizeof(ackPacket), 0, (struct sockaddr*) &cliAddr, &cliLen);
        if (errno == EINTR) {   /*Alarm went off*/
            if (tries < MAXTRIES) { /*tries incremented by signal handler*/
                daprint("ALARM went off while waiting on ACK from client about initResponse: %i more tries\n", MAXTRIES-tries);
                /*NEED TO RESEND initResponse-taken care of by do-while statement*/
                alarm(MAX_ALARM_TIME);
            } else {
                printf("ALARM went off, and we have exceeded MAXTRIES. Giving up sending initResponse packet.\n")
                fflush(stdout);
                /*NOTE: Do we need to reset server state?*/
                break;
            }
        } else {
            printf("recvfrom() failed while listening for ACK from initResponse");
        }
        /*RECVFROM got something -disable alarm*/
        alarm(0);
        /*We need to make sure what we have is an ACK packet*/
        memmove((void*) &ack, (void*) recvBuffer, rv);
        sanityFlag = 0;
        progIDCheck = ntohl(clientParams.progID);
        if (progIDCheck == 901841089) {
            sanityFlag = success = 1;
            dprint("We have successfully received the ACK from initResponse!\n");
        }
    }
    } while(!success)
    
    /*Time to send the file*/
    
    /*EVERYTHING BELOW HERE IS NOT FINISHED*/
    
    
    while(clientInit) {
        /*initialize the SIGALRM for our timer*/
        if (clientInit) {
            if (SIG_ERR == signal(SIGALRM, catchAlarm)) {
                printf("SIGALRM trap failed to set.\n");
                fflush(stdout);
            } else {
                alarm(MAX_ALARM_TIME);
            }
        }
        /*block until data is received*/
        recvMsgSize = recvfrom(sock, recvBuffer, sizeof(initPacket), 0, (struct sockaddr*) &cliAddr, &cliLen);
        
        if (errno = EINTR) {
            dprint("Too many problems--reseting server state\n");
            flushbuffer();
            fclose(file);
            file = NULL;
            clientInit = 0;
            ackSeqNum = seqNum = 0;
            errno = 0;
            continue;
        } else if (recvMsgSize == -1) {
            /*Unset alarm b/c socket had an error recving*/
            alarm(0);
            printf("Error receiving from the client.\n");
            fflush(stdout);
        } else {
            /*Process data*/
            alarm(0);
        }
    }
    
    
    /*wait for a connection request*/
    
    
    if ((recvMsgSize = recvfrom(sock, recvBuffer, 1024, 0, (struct sockaddr*) &cliAddr, &cliLen)) < 0) {
        /*closeSocket(sock);
        DieWithError("initial recvfrom() failed");*/
        /*Go ahead and giveup-assume client will timeout and resend*/
        daprint("Something sent us a packet, but it wasn't the expected init packet size. The size was: %i.\n", recvMsgSize);
    }
    else {
        /*Since we successfully recv'd the client initialization packet--process it*/
        if (debug) printf("Handling client %s\n", inet_ntoa(cliAddr.sin_addr));
        clientInit = 1;
        
        
    }
    
    if(clientInit = 1)
    do  /* As long as there is input... */
    {
        /* Set the size of the in-out parameter */
        cliLen = sizeof(cliAddr);

        if ((recvMsgSize = recvfrom(sock, recvBuffer, MAX_BUFFER_SIZE, 0,
               (struct sockaddr *) &cliAddr, &cliLen)) < 0)
        {
            /* Only acceptable error: recvfrom() would have blocked */
            if (errno != EWOULDBLOCK)  
                DieWithError("recvfrom() failed");
        }
        else
        {
            printf("Handling client %s\n", inet_ntoa(cliAddr.sin_addr));

            if (sendto(sock, recvBuffer, recvMsgSize, 0, (struct sockaddr *) 
                  &echoClntAddr, sizeof(echoClntAddr)) != recvMsgSize)
                DieWithError("sendto() failed");
        }
    }  while (recvMsgSize >= 0);
    /* Nothing left to receive */
}
